package com.ryenlii.datatrans.core.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.ryenlii.datatrans.common.i18n.I18nMessageUtil;
import com.ryenlii.datatrans.common.i18n.LanguageEnum;
import com.ryenlii.datatrans.core.*;
import com.ryenlii.datatrans.core.exception.ParamsException;
import com.ryenlii.datatrans.core.exception.ServiceException;
import com.ryenlii.datatrans.core.util.RedisUtil;
import com.ryenlii.datatrans.core.interceptor.MyHandlerInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import jakarta.annotation.Resource;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.*;

/**
 * Spring MVC 配置
 *
 * @author zhengwen
 */
@Configuration
@Slf4j
public class MyWebMvcConfigurer implements WebMvcConfigurer {

    /**
     * 当前激活的配置文件
     */
    @Value("${spring.profiles.active}")
    private String env;

    /**
     * 免校验url
     */
    @Value("${web.pass.url}")
    private String webPassUrl;

    /**
     * 全局时间字段格式
     */
    @Value("${spring.global.date-format:'yyyy-MM-dd HH:mm:ss'}")
    private String globalDateFormat;

    @Resource
    RedisUtil redisUtil;

    /**
     * 免校验的请求
     */
    private Set<String> allowUrlSet = new HashSet<String>();

    /**
     * 使用阿里 FastJson 作为JSON MessageConverter
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        FastJsonConfig config = new FastJsonConfig();
        //设置时间字段统一格式
        //config.setDateFormat(globalDateFormat);

        config.setSerializerFeatures(
                SerializerFeature.DisableCircularReferenceDetect,
                SerializerFeature.PrettyFormat,
                SerializerFeature.WriteNullStringAsEmpty,
                SerializerFeature.WriteNullListAsEmpty);
        converter.setFastJsonConfig(config);
        converter.setDefaultCharset(Charset.forName("UTF-8"));
        converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON));
        converters.add(0, converter);
        WebMvcConfigurer.super.configureMessageConverters(converters);
    }


    /**
     * 统一异常处理
     *
     * @param exceptionResolvers
     */
    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
        exceptionResolvers.add(new HandlerExceptionResolver() {
            @Override
            public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
                Result result = new Result();
                if (e instanceof ServiceException) {//业务失败的异常，如“账号或密码错误”
                    result.setCode(ResultCode.FAIL).setMessage(e.getMessage());
                    log.info(e.getMessage());
                } else if (e instanceof NoHandlerFoundException) {
                    result.setCode(ResultCode.NOT_FOUND).setMessage("接口 [" + request.getRequestURI() + "] 不存在");
                } else if (e instanceof ServletException) {
                    result.setCode(ResultCode.FAIL).setMessage(e.getMessage());
                } else if (e instanceof MethodArgumentNotValidException) {
                    String message = ((MethodArgumentNotValidException) e).getBindingResult().getFieldError().getDefaultMessage();
                    result.setCode(ResultCode.FAIL).setMessage(message);
                } else if (e instanceof ParamsException) {
                    result.setCode(ResultCode.FAIL).setMessage(getMessage(e.getMessage(), request));
                } else {
                    String respError = "接口 [" + request.getRequestURI() + "] 内部错误，请联系管理员。";
                    if (StringUtils.isNotBlank(e.getMessage())) {
                        respError += "错误原因[" + e.getMessage() + "]";
                    }
                    result.setCode(ResultCode.INTERNAL_SERVER_ERROR).setMessage(respError);
                    String message;
                    if (handler instanceof HandlerMethod) {
                        HandlerMethod handlerMethod = (HandlerMethod) handler;
                        message = String.format("接口 [%s] 出现异常，方法：%s.%s，异常摘要：%s",
                                request.getRequestURI(),
                                handlerMethod.getBean().getClass().getName(),
                                handlerMethod.getMethod().getName(),
                                e.getMessage());
                    } else {
                        message = e.getMessage();
                    }
                    log.error(message, e);
                }
                responseResult(response, result);
                return new ModelAndView();
            }

        });
    }

    /**
     * 解决跨域问题
     *
     * @param registry
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**").allowedOrigins("*")
                .allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")
                .allowedHeaders("*")
                .maxAge(3600);
    }


    /**
     * 添加拦截器
     *
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //接口签名认证拦截器，该签名认证比较简单，实际项目中可以使用Json Web Token或其他更好的方式替代。
        initPassUrl();
        registry.addInterceptor(new MyHandlerInterceptor() {
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                //jwt token认证处理
                boolean pass = jwtTokenAuthVaild(request, response);
                if (pass) {
                    /// 反向获取用户信息  设置用户信息
                    //request.setAttribute("", "");
                    log.info("---jwt校验通过");
                    return true;
                } else {
                    log.warn("token认证失败，请求接口：{}，请求IP：{}，请求参数：{}",
                            request.getRequestURI(), getIpAddress(request), JSON.toJSONString(request.getParameterMap()));

                    Result result = new Result();
                    result.setCode(ResultCode.UNAUTHORIZED).setMessage("请求认证无权限或认证失败");
                    responseResult(response, result);
                    return false;
                }
            }
        });
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("forward:/index");
        registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
        WebMvcConfigurer.super.addViewControllers(registry);
    }

    /**
     * json web token 授权校验
     *
     * @param request  请求入参对象
     * @param response 请求respones
     * @return 布尔值 true通过，false不通过
     */
    private boolean jwtTokenAuthVaild(HttpServletRequest request, HttpServletResponse response) {

        String url = request.getRequestURI();
        log.info("----请求的url:{}", url);
        if (isOptionRequest(request)) {
            response.setStatus(200);
            return true;
        }
        if (isTokenCheckPassUrl(request)) {
            log.info("---不校验token的请求");
            return true;
        }

        String token = request.getHeader("X-Token");
        if (StringUtils.isBlank(token)) {
            return false;
        }

        return true;
    }

    /**
     * 是否OPTIONS请求
     *
     * @param request
     * @return
     */
    private boolean isOptionRequest(HttpServletRequest request) {
        String method = request.getMethod();
        if ("OPTIONS".equals(method)) {
            return true;
        }
        return false;
    }

    /**
     * response结果
     *
     * @param response
     * @param result
     */
    private void responseResult(HttpServletResponse response, Result result) {
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Content-type", "application/json;charset=UTF-8");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type,x-requested-with,X-Token");
        response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD");
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setStatus(200);
        try {
            response.getWriter().write(JSON.toJSONString(result, SerializerFeature.WriteNullStringAsEmpty));
        } catch (IOException ex) {
            log.error(ex.getMessage());
        }
    }

    /**
     * 获取真实IP
     *
     * @param request
     * @return
     */
    public static String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        // 如果是多级代理，那么取第一个ip为客户端ip
        if (ip != null && ip.indexOf(",") != -1) {
            ip = ip.substring(0, ip.indexOf(",")).trim();
        }

        return ip;
    }

    /**
     * 初始化不校验token请求
     */
    private void initPassUrl() {
        if (StringUtils.isNotBlank(webPassUrl)) {
            if (webPassUrl.indexOf(",") < 0) {
                allowUrlSet.add(webPassUrl);
            } else {
                String[] splitUrl = webPassUrl.split(",");
                for (String url : splitUrl) {
                    if (StringUtils.isNotBlank(url)) {
                        allowUrlSet.add(url);
                    }
                }
            }
        }

    }

    /**
     * 允许通过的url
     *
     * @param request
     * @return
     */
    private boolean isTokenCheckPassUrl(HttpServletRequest request) {
        String realPath = request.getServletContext().getContextPath();
        String requestUrl = request.getRequestURI();
        String redirectPath = requestUrl.substring(requestUrl.indexOf(realPath) + realPath.length());
        for (String reg : allowUrlSet) {
            reg = reg.replaceAll("//", "\\/");
            PathMatcher pathMatcher = new AntPathMatcher();
            if (pathMatcher.match(reg, redirectPath)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取返回的message
     *
     * @param message
     * @param request
     * @return
     */
    private String getMessage(String message, HttpServletRequest request) {
        Map map = (Map) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
        String lang = (String) map.get("lang");
        String language = LanguageEnum.LANGUAGE_ZH_CN.getLanguage();
        if (StringUtils.isNotBlank(lang)) {
            language = LanguageEnum.getLanguageType(lang);
        }
        if (StringUtils.isBlank(language)) {
            message = "Response Msg Error! This language not support!";
        } else {
            try {
                message = I18nMessageUtil.getMessage(language, message, "FAIL");
            } catch (IOException e1) {
                message = "language Msg get Fail!";
            }
        }
        return message;
    }

}
